package android.remotematrixclient.objclient;


import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.OptionalDataException;
import java.io.StreamCorruptedException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import android.remotematrixclient.objclient.CommandWrap;
import android.remotematrixclient.objclient.clientevent.ObjEvent;
import android.remotematrixclient.objclient.clientevent.ObjEventListener;
/**
 * La clase <code>Receiver</code> implementa un servicio de recepción de 
 * objetos serializados. <code>Receiver</code> implementa la interfase 
 * <code>Runnable</code> que permite ejecutar las instancias en hilos. También
 * posee una lista enlazada de los commandos que llegan y genera un evento de 
 * tipo <code>ObjEventListener</code> para poder avisar todos los objetos que 
 * se han registrado de los eventos ocurridos.
 * 
 * @author Rafael Asprilla
 */
public class Receiver implements Runnable{
	
	/**
	 * Flujo de objetos de entrada.
	 */
	private ObjectInputStream in;
	
	/**
	 * Lista enlazada de comandos recibidos hasta el momento.
	 */
	private LinkedList<CommandWrap> inbound;
	
	/**
	 * Lista de objetos tipo <code>ObjEventListener</code> registrados en 
	 * <code>Receiver</code>. Cada vez que ocurre un evento se le notifica a 
	 * todos los miembros de esta lista.
	 */
	private List<ObjEventListener> listener;
	
	/**
	 * Esta propiedad define si la conexión que crea este objeto todavía está 
	 * activa.
	 */
	private boolean connected = true;
	
	/**
	 * Constructor único de la clase <code>Receiver</code>.
	 * @param client
	 * Es el objeto de tipo <code>Socket</code> que inicializa la conexión.
	 */
	public Receiver(Socket client){
		
		try {
			in = new ObjectInputStream(client.getInputStream());
		} catch (StreamCorruptedException e) {
			e.printStackTrace();
			System.exit(-1);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(-1);
		}
		inbound = new LinkedList<CommandWrap>();
		listener = new ArrayList<ObjEventListener>();
	}
	
	/**
	 * Agrega un objeto de tipo <code>ObjEventListener</code> a la lista de 
	 * objetos que reciben notificaciones de tipo <code>ObjEvent</code>.
	 * 
	 * @param l
	 * Objeto de tipo <code>ObjEventListener</code>.
	 */
	public synchronized void addListener(ObjEventListener l){
		listener.add(l);
	}
	
	/**
	 * Remueve el objeto de tipo <code>ObjEventListener</code> de la lista de
	 * notificaciones de eventos de tipo <code>ObjEvent</code>.
	 * 
	 * @param l
	 * Objeto de tipo <code>ObjEventListener</code>.
	 */
	public synchronized void removeListener(ObjEventListener l){
		listener.remove(l);
	}
	
	/**
	 * El método <code>trigger()</code> notifica a todos los objetos 
	 * registrados en la lista <code>listener</code> de que ha ocurrido un 
	 * evento.
	 */
	private synchronized void trigger(){
		ObjEvent evt = new ObjEvent(this);
		Iterator<ObjEventListener> i = listener.iterator();
		while(i.hasNext()){
			((ObjEventListener)i.next()).handleClientEvent(evt);
		}
	}
	
	/**
	 * Devuelve el número de objetos que se encuentran en la lista de entrada 
	 * actualmente.
	 * 
	 * @return
	 * Número de objetos almacenados en la lista de entrada.
	 */
	public int size(){
		return inbound.size();
	}
	
	/**
	 * Devuelve un objeto de tipo <code>CommandWrap</code> y lo elimina de la 
	 * lista enlazada de objetos de entrada.
	 * 
	 * @return
	 * El commando y los datos que llevan más tiempo en la pila.
	 */
	public CommandWrap get(){
		return inbound.remove();
	}

	/**
	 * Cambia el estado de <code>connected</code> y esto inicia la secuencia de
	 * cierre de la conexión. 
	 */
	public void close(){
		connected = false;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		while(connected){
			Object obj = null;

			try {
				obj = in.readObject();
			} catch (EOFException e){
				
			} catch (OptionalDataException e) {
				e.printStackTrace();
				break;
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				break;
			} catch (IOException e) {
				e.printStackTrace();
				break;
			}
			
			if(obj instanceof CommandWrap){
				inbound.add((CommandWrap) obj);
				trigger();
			}
		}
		try {
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(-1);
		}
	}
	
}
